/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package org.dafe.partyband.gui.concerts;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.lang.ref.WeakReference;
import java.util.Collection;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import org.dafe.partyband.db.api.IConcert;
import org.dafe.partyband.db.api.ISong;
import org.dafe.partyband.services.api.IDurationObtainer;
import org.openide.util.Lookup.Result;
import org.openide.util.*;
import org.openide.util.RequestProcessor.Task;

/**
 *
 * @author simda15
 */
public class DurationsPanel extends JPanel implements LookupListener, TableModelListener, ActionListener {
    private static final IDurationObtainer durationObtainer = Lookup.getDefault().lookup(IDurationObtainer.class);
    private final Result<ISong> selSongRes;
    private final IConcert concert;
    
    private int songBreakDuration = 45;
    private int concertBreakDuration = 15 * 60;
    private int concertBreakCount = 2;
    private int defaultSongDuration = 3 * 60 + 30;
    
    private Timer coalesceTimer = new Timer(500, this);
    
    private RequestProcessor ConcertRP = new RequestProcessor(DurationsPanel.class.getName() + "-concert", 1, true);
    private RequestProcessor SelectedRP = new RequestProcessor(DurationsPanel.class.getName() + "-selected", 1, true);

    private WeakReference<Task> previousSelectedTask;
    private WeakReference<Task> previousConcertTask;
    
    private static final String IN_PROGRESS = NbBundle.getMessage(DurationsPanel.class, "DurationsPanel.inProgress");
    
    /**
     * Creates new form DurationsPanel
     */
    public DurationsPanel(PlaylistPanel playlist, IConcert concert) {
        this.concert = concert;
        initComponents();
        
        selSongRes = playlist.getSelSongLookup().lookupResult(ISong.class);
        selSongRes.addLookupListener(this);
        
        playlist.getPlaylistTable().getModel().addTableModelListener(this);

        coalesceTimer.setRepeats(false);
        
        // force computation of concert duration
        tableChanged(null);
    }

    public int getConcertBreakDuration() {
        return concertBreakDuration;
    }

    public void setConcertBreakDuration(int concertBreakDuration) {
        this.concertBreakDuration = concertBreakDuration;
    }

    public int getSongBreakDuration() {
        return songBreakDuration;
    }

    public void setSongBreakDuration(int songBreakDuration) {
        this.songBreakDuration = songBreakDuration;
    }

    public int getConcertBreakCount() {
        return concertBreakCount;
    }

    public void setConcertBreakCount(int concertBreakCount) {
        this.concertBreakCount = concertBreakCount;
    }
    
    public int getDefaultSongDuration() {
        return defaultSongDuration;
    }

    public void setDefaultSongDuration(int defaultSongDuration) {
        this.defaultSongDuration = defaultSongDuration;
    }
    
    private void setConcertDurations(int rawDuration, int durationWithBreaks) {
        concertRaw.setText(duration2Text(rawDuration));
        concertWithBreaks.setText(duration2Text(durationWithBreaks));
    }
    
    private void setConcertDurationIsInProgress() {
        concertRaw.setText(IN_PROGRESS);
        concertWithBreaks.setText(IN_PROGRESS);
    }
    
    private void setSelSongsDurations(int rawDuration, int durationWithBreaks) {
        selSongsRaw.setText(duration2Text(rawDuration));
        selSongsWithBreaks.setText(duration2Text(durationWithBreaks));
    }
    
    private void setSelSongsDurationIsInProgress() {
        selSongsRaw.setText(IN_PROGRESS);
        selSongsWithBreaks.setText(IN_PROGRESS);
    }

    /**
     * This method is called from within the constructor to initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is always
     * regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        concertLabel = new org.jdesktop.swingx.JXLabel();
        concertRawLabel = new org.jdesktop.swingx.JXLabel();
        concertWithBreaksLabel = new org.jdesktop.swingx.JXLabel();
        selSongsLabel = new org.jdesktop.swingx.JXLabel();
        selSongsRawLabel = new org.jdesktop.swingx.JXLabel();
        selSongsWithBreaksLabel = new org.jdesktop.swingx.JXLabel();
        concertWithBreaks = new org.jdesktop.swingx.JXLabel();
        concertRaw = new org.jdesktop.swingx.JXLabel();
        selSongsWithBreaks = new org.jdesktop.swingx.JXLabel();
        selSongsRaw = new org.jdesktop.swingx.JXLabel();

        concertLabel.setText(org.openide.util.NbBundle.getMessage(DurationsPanel.class, "DurationsPanel.concertLabel.text")); // NOI18N

        concertRawLabel.setText(org.openide.util.NbBundle.getMessage(DurationsPanel.class, "DurationsPanel.concertRawLabel.text")); // NOI18N

        concertWithBreaksLabel.setText(org.openide.util.NbBundle.getMessage(DurationsPanel.class, "DurationsPanel.concertWithBreaksLabel.text")); // NOI18N

        selSongsLabel.setText(org.openide.util.NbBundle.getMessage(DurationsPanel.class, "DurationsPanel.selSongsLabel.text")); // NOI18N

        selSongsRawLabel.setText(org.openide.util.NbBundle.getMessage(DurationsPanel.class, "DurationsPanel.selSongsRawLabel.text")); // NOI18N

        selSongsWithBreaksLabel.setText(org.openide.util.NbBundle.getMessage(DurationsPanel.class, "DurationsPanel.selSongsWithBreaksLabel.text")); // NOI18N

        concertWithBreaks.setText(org.openide.util.NbBundle.getMessage(DurationsPanel.class, "DurationsPanel.concertWithBreaks.text")); // NOI18N

        concertRaw.setText(org.openide.util.NbBundle.getMessage(DurationsPanel.class, "DurationsPanel.concertRaw.text")); // NOI18N

        selSongsWithBreaks.setText(org.openide.util.NbBundle.getMessage(DurationsPanel.class, "DurationsPanel.selSongsWithBreaks.text")); // NOI18N

        selSongsRaw.setText(org.openide.util.NbBundle.getMessage(DurationsPanel.class, "DurationsPanel.selSongsRaw.text")); // NOI18N

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
        this.setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(concertLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addGroup(layout.createSequentialGroup()
                        .addGap(10, 10, 10)
                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                            .addComponent(concertWithBreaksLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                            .addComponent(concertRawLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
                            .addComponent(concertRaw, javax.swing.GroupLayout.DEFAULT_SIZE, 117, Short.MAX_VALUE)
                            .addComponent(concertWithBreaks, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))))
                .addGap(11, 11, 11)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(selSongsLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addGroup(layout.createSequentialGroup()
                        .addGap(10, 10, 10)
                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                            .addComponent(selSongsWithBreaksLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                            .addComponent(selSongsRawLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                            .addComponent(selSongsWithBreaks, javax.swing.GroupLayout.DEFAULT_SIZE, 239, Short.MAX_VALUE)
                            .addComponent(selSongsRaw, javax.swing.GroupLayout.DEFAULT_SIZE, 239, Short.MAX_VALUE))))
                .addContainerGap())
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(concertLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addComponent(selSongsLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(concertRawLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addComponent(selSongsRawLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addComponent(concertRaw, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addComponent(selSongsRaw, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(concertWithBreaksLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addComponent(selSongsWithBreaksLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addComponent(concertWithBreaks, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addComponent(selSongsWithBreaks, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
        );
    }// </editor-fold>//GEN-END:initComponents
    // Variables declaration - do not modify//GEN-BEGIN:variables
    private org.jdesktop.swingx.JXLabel concertLabel;
    private org.jdesktop.swingx.JXLabel concertRaw;
    private org.jdesktop.swingx.JXLabel concertRawLabel;
    private org.jdesktop.swingx.JXLabel concertWithBreaks;
    private org.jdesktop.swingx.JXLabel concertWithBreaksLabel;
    private org.jdesktop.swingx.JXLabel selSongsLabel;
    private org.jdesktop.swingx.JXLabel selSongsRaw;
    private org.jdesktop.swingx.JXLabel selSongsRawLabel;
    private org.jdesktop.swingx.JXLabel selSongsWithBreaks;
    private org.jdesktop.swingx.JXLabel selSongsWithBreaksLabel;
    // End of variables declaration//GEN-END:variables

    public void resultChanged(LookupEvent le) {
        // we need coalescing as selection changes can come very frequently
        coalesceTimer.restart();
    }

    public void actionPerformed(ActionEvent e) {
        if (previousSelectedTask != null) {
            Task previous = previousSelectedTask.get();
            if (previous != null) {
                previous.cancel();
            }
        }
        
        setSelSongsDurationIsInProgress();
                
        previousSelectedTask = new WeakReference<Task>(SelectedRP.post(new Runnable() {
            public void run() {
                final Collection<? extends ISong> selInstances = selSongRes.allInstances(); 
                final int rawDuration = computeDuration(selInstances, false, false);
                final int durationWithBreaks = computeDuration(selInstances, true, false);
                
                SwingUtilities.invokeLater(new Runnable() {
                    public void run() {
                        setSelSongsDurations(rawDuration, durationWithBreaks);
                    }
                });
            }
        }));
    }
    
    public final void tableChanged(TableModelEvent e) {
        if (previousConcertTask != null) {
            Task previous = previousConcertTask.get();
            if (previous != null) {
                previous.cancel();
            }
        }
        
        setConcertDurationIsInProgress();
        
        previousConcertTask = new WeakReference<Task>(ConcertRP.post(new Runnable() {
            public void run() {
                final Collection<? extends ISong> concertSongs = concert.getSongs(); 
                final int rawDuration = computeDuration(concertSongs, false, false);
                final int durationWithBreaks = computeDuration(concertSongs, true, false);
                
                SwingUtilities.invokeLater(new Runnable() {
                    public void run() {
                        setConcertDurations(rawDuration, durationWithBreaks);
                    }
                });
            }
        }));
    }
    
    private int computeDuration(Collection<? extends ISong> songs, boolean includeBreaks, boolean concert) {
        int result = 0;
        for (ISong song : songs) {
            int curDuration = durationObtainer.getDuration(song);
            
            // allow for canceling of obsolete computations
            if (Thread.interrupted()) {
                System.err.println("Interrupted on song " + song.getName() + ", thread " + Thread.currentThread().getName());
                return 0;
            }
            
            if (curDuration <= 0) {
                curDuration = defaultSongDuration;
            }
            // not include break for the very first song
            if (includeBreaks && result > 0) {
                result += songBreakDuration;
            }
            result += curDuration;
        }
        if (concert && result > 0 && includeBreaks) {
            result += concertBreakCount * concertBreakDuration - 
                    concertBreakCount * songBreakDuration;
        }
        
        return result;
    }
    
    /**
     * Transforms time in seconds into human readable string
     * 
     * @param duration input time in seconds
     * @return human readable string
     */
    private static String duration2Text(int duration) {
        if (duration == 0) {
            return "---";
        }
        
        StringBuilder sb = new StringBuilder();
        int hours = duration / 3600;
        // TODO - resolve plurals
        if (hours > 0) {
            sb.append(hours).append(" ").append(NbBundle.getMessage(DurationsPanel.class, "DurationsPanel.hour", hours)).append(" ");
            duration = duration % 3600;
        }
        
        int minutes = duration / 60;
        if (hours > 0 || minutes > 0) {
            sb.append(minutes).append(" ").append(NbBundle.getMessage(DurationsPanel.class, "DurationsPanel.minute")).append(" ");
            duration = duration % 60;
        }
        
        sb.append(duration).append(" ").append(NbBundle.getMessage(DurationsPanel.class, "DurationsPanel.second"));
        
        return sb.toString();
    }

}
